package sjm.parse;

import java.util.*;
import lombok.Getter;
import lombok.Setter;
/*
 * Copyright (c) 1999 Steven J. Metsker. All Rights Reserved.
 * 
 * Steve Metsker makes no representations or warranties about
 * the fitness of this software for any particular purpose, 
 * including the implied warranty of merchantability.
 */

/**
 * An assembly maintains a stream of language elements along with stack and target objects.
 *
 * Parsers use assemblers to record progress at recognizing language elements from assembly's string.
 *
 * @author Steven J. Metsker
 *
 * @version 1.0
 *
 */
public abstract class Assembly implements PubliclyCloneable {

    /*
     * a place to keep track of consumption progress
     */
    @Getter
    protected Stack stack = new Stack();

    /** 
     * Another place to record progress; this is just an object. 
     * If a parser were recognizing an HTML page, for 
     * example, it might create a Page object early, and store it 
     * as an assembly's "target". As its recognition of the HTML 
     * progresses, it could use the stack to build intermediate 
     * results, like a heading, and then apply them to the target 
     * object.
     */
    @Setter @Getter
    private PubliclyCloneable target;

    /**
     * which element is next
     */
    protected int index = 0;

    /**
     * Return a copy of this object.
     *
     * @return a copy of this object
     */
    @Override
    public Object clone() {
        try {
            Assembly a = (Assembly) super.clone();
            a.stack = (Stack) stack.clone();
            if (target != null) {
                a.target = (PubliclyCloneable) target.clone();
            }
            return a;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError("Clone is not supported!");
        }
    }

    /**
     * Returns the elements of the assembly that have been consumed, separated by the specified delimiter.
     *
     * @param delimiter the mark to show between consumed elements
     *
     * @return the elements of the assembly that have been consumed
     */
    public abstract String consumed(String delimiter);

    /**
     * Returns the default string to show between elements.
     *
     * @return the default string to show between elements
     */
    public abstract String defaultDelimiter();

    /**
     * Returns the number of elements that have been consumed.
     *
     * @return the number of elements that have been consumed
     */
    public int elementsConsumedNr() {
        return index;
    }

    /**
     * @return the number of elements that have not been consumed
     */
    public int elementsRemainNr() {
        return length() - elementsConsumedNr();
    }

    /**
     * @return true, if this assembly has un-consumed elements
     */
    public boolean hasElementsRemain() {
        return elementsConsumedNr() < length();
    }

    public abstract Object next();

    /**
     * Returns the number of elements in this assembly.
     *
     * @return the number of elements in this assembly
     */
    public abstract int length();

    /**
     * Shows the next object in the assembly, without removing it
     *
     * @return the next object
     *
     */
    public abstract Object peek();

    /**
     * Removes the object at the top of this assembly's stack and returns it.
     *
     * @return the object at the top of this assembly's stack
     *
     * @exception EmptyStackException if this stack is empty
     */
    public Object pop() {
        return stack.pop();
    }

    /**
     * Pushes an object onto the top of this assembly's stack.
     *
     * @param o the object to be pushed
     */
    public void push(Object o) {
        stack.push(o);
    }

    /**
     * Returns the elements of the assembly that remain to be consumed, separated by the specified delimiter.
     *
     * @param delimiter the mark to show between unconsumed elements
     *
     * @return the elements of the assembly that remain to be consumed
     */
    public abstract String remainder(String delimiter);

    /**
     * Returns true if this assembly's stack is empty.
     *
     * @return true, if this assembly's stack is empty
     */
    public boolean stackIsEmpty() {
        return stack.isEmpty();
    }

    /**
     * Returns a textual description of this assembly.
     *
     * @return a textual description of this assembly
     */
    @Override
    public String toString() {
        String delimiter = defaultDelimiter();
        return stack + consumed(delimiter) + "^" + remainder(delimiter);
    }
}
